# coding: utf-8

# region #引入开发包
import binascii
import os
# following imports are required by PKI
import Crypto
import Crypto.Random
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5  # 用于签名
from Crypto.Hash import SHA  # 用于签名
import hashlib  # 用于hash计算
import configparser  # 处理ini文件
import time
import datetime
import base64
import json  # 处理JSON数据
import sqlite3  # 数据库
import threading #线程

# endregion
# region #定义数据结构
dsTranPart1 = {
    'sender': '',
    'recipient': '',
    'value': 0,
    'fee': 0,
    'timestamp': '',
    'scriptstr': '',
    'specinfo': ''
}  # 转账交易的数据结构1
dsTranPart2 = {
    'part1Str': '',
    'signStr': '',
    'pubkeyStr': ''
}  # 转账交易的数据结构2
dsTranPart3 = {
    'part2Str': '',
    'tranHash': ''
}  # 转账交易的数据结构3
dsBlockPart1 = {
    'blockId': '',
    'previousBlockHash': 0,
    'blockTranStr': [],
    'minerAddress': '',
    'miningRandomNum': 0,
    'miningMsg': '',
    'miningDifficulty': 0,
    'timestamp': ''
}  # 区块的前一部分，不带该区块的HASH值
dsBlockPart2 = {
    'part1Str': '',
    'thisBlockHash': ''
}  # 整个区块部分

MiningDifficulty = 10  # 挖矿难度，表示HASH最前面至少要有多少个0
DftTheta = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF >> MiningDifficulty  # 右移
# print(hex(DftTheta))
# endregion

# region #定义文件路径
txtFileName = 'TranscationLists.txt'  # 用于存储交易信息
rootPath = os.path.abspath('.')  # current dir
# blockDir = blockRootPath + '/SwuBlockchain/block'  # block file directory
BlockDir = rootPath + '/block'  # block file directory
if not os.path.exists(BlockDir):
    os.makedirs(BlockDir)
accountDir = rootPath + '/account'  # block file directory
if not os.path.exists(accountDir):
    os.makedirs(accountDir)
DbBaseDir = rootPath + '/BlockDb'  # block file directory
if not os.path.exists(DbBaseDir):
    os.makedirs(DbBaseDir)    
BlockDbFilename = DbBaseDir+'/BlockDb.db'    # 区块数据库文件名
# endregion

def LoadCsiJsonStr():
    _JsonFileName =   r'F:\Storage\FileDataSet\49fc2b67fc45787a90a0dc1eb6873ddc396c1c00_CSI.json'
    _FileObjJson = open(_JsonFileName, 'r+')
    _JsonDataCsi = json.loads(_FileObjJson.read())
    _FileObjJson.close()
    return _JsonDataCsi

def FilterInput(strInput=''):  # 对cmd输入的命令进行过滤
    outputStr = strInput.replace("\t", " ")  # 替换table键为空格
    outputStr = outputStr.replace("\n", " ")  # 替换enter键为空格
    outputStr = outputStr.replace('  ', ' ')  # 两个空格替换为一个空格
    outputStr = outputStr.strip()
    return outputStr


def CheckAccountName(accountName=''):  # 查找账户名称是否合法
    rtnResult = True
    if len(accountName) > 30:  # 不能超过30个字符
        rtnResult = False
    for strA in accountName:
        if strA not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz':
            rtnResult = False
    return rtnResult


def FindAllAccounts(filesPath):  # 查找指定文件夹下的所有账户
    if not os.path.exists(filesPath):
        os.makedirs(filesPath)
    files = os.listdir(filesPath)
    accountList = []
    for s in files:
        sPath = os.path.join(filesPath, s)
        if os.path.isdir(sPath):
            FindBlockFiles(sPath, accountList)
        elif os.path.isfile(sPath) and '_Public' in s:
            #取出文件名
            fileName = os.path.basename(sPath)
            accountName = fileName.split('_')[0]
            accountList.append(accountName)
    return accountList


def FindTranFiles(filesPath):  # 查找指定文件夹下的交易
    # 查找文件代码
    if not os.path.exists(filesPath):
        os.makedirs(filesPath)
    files = os.listdir(filesPath)
    filesList = []
    for s in files:
        sPath = os.path.join(filesPath, s)
        if os.path.isdir(sPath):
            FindTranFiles(sPath, filesList)
        elif os.path.isfile(sPath) and 'tran' in s:
            filesList.append(sPath)
    return filesList


def FindBlockFiles(filesPath):  # 查找指定文件夹下的区块链文件
    # 查找文件代码
    if not os.path.exists(filesPath):
        os.makedirs(filesPath)
    files = os.listdir(filesPath)
    filesList = []
    for s in files:
        sPath = os.path.join(filesPath, s)
        if os.path.isdir(sPath):
            FindBlockFiles(sPath, filesList)
        elif os.path.isfile(sPath) and 'block' in s:
            filesList.append(sPath)
    return filesList


def UpdateBlockDb():  # 根据文件夹下的区块文件，更新区块数据库表“block”
    global BlockDbFilename
    dbFilename = BlockDbFilename
    dbConn = sqlite3.connect(dbFilename)  # 数据库连接
    c = dbConn.cursor()
    listFiles = FindBlockFiles(BlockDir)
    intFileNum = len(listFiles)
    if intFileNum == 0:
        GenerateZeroBlock()  # 产生0号区块
    listFiles = FindBlockFiles(BlockDir)
    intFileNum = len(listFiles)
    for i in range(0, intFileNum, 1):
        curFilename = listFiles[i]
        strBasename = os.path.basename(curFilename)
        #判断该文件是否已经在数据库中
        strSql = r"SELECT * FROM [Block] WHERE [B_Filename]='%s'" % strBasename
        c.execute(strSql)
        intRows = len(c.fetchall())
        if intRows > 0:
            continue  # 已存在
        vResult = VerifyBlock(curFilename)
        if vResult:
            f = open(curFilename, "r")
            str1 = f.read()  # 每个区块文件只包含一笔交易
            f.close()
            if len(str1) > 0:
                _dsBlockPart2 = json.loads(str1)
                blockHash = _dsBlockPart2['thisBlockHash']
                _dsBlockPart1 = json.loads(_dsBlockPart2['part1Str'])
                blockId = int(_dsBlockPart1['blockId'])
                if blockId > 0:
                    PrevBlockID = blockId-1
                else:
                    PrevBlockID = 0
                blockTimestamp = _dsBlockPart1['timestamp']
                previousBlockHash = _dsBlockPart1['previousBlockHash']

            # 处理数据库
            strSql = r"INSERT INTO [Block]([B_Filename],[B_BlockID],[B_BlockHash],[B_Timestamp],[B_PrevBlockID],[B_PrevBlockHash],[B_Confirmed]) VALUES('%s',%s,'%s',%s,%s,'%s',%s)" % (
                strBasename, blockId, blockHash, blockTimestamp, PrevBlockID, previousBlockHash, 0)
            c.execute(strSql)

            dbConn.commit()  # 对数据库有修改，必须commit
    dbConn.close()

    return


def FindAllTranHashes(blockFilePath):  # 找出一个区块文件中包含的所有交易hash，返回列表
    rtnHashList = []
    curFilename = blockFilePath
    vResult = VerifyBlock(curFilename)
    if vResult:
        f = open(curFilename, "r")
        str1 = f.read()  # 每个区块文件只包含一笔交易
        f.close()
        if len(str1) > 0:
            _dsBlockPart2 = json.loads(str1)
            blockHash = _dsBlockPart2['thisBlockHash']
            _dsBlockPart1 = json.loads(_dsBlockPart2['part1Str'])
            blockId = int(_dsBlockPart1['blockId'])
            if blockId > 0:
                blockTimestamp = _dsBlockPart1['timestamp']
                for tranStr in _dsBlockPart1['blockTranStr']:
                    _dsTranPart3 = json.loads(tranStr)
                    rtnHashList.append(_dsTranPart3['tranHash'])

    return rtnHashList


def UpdateTransactionInfoDb():
    # 根据文件夹下的交易文件，更新交易信息数据库
    global BlockDbFilename
    dbFilename = BlockDbFilename
    dbConn = sqlite3.connect(dbFilename)  # 数据库连接
    c = dbConn.cursor()

    # 找出已存在的区块，将这些区块中包含的交易设置为已处理完毕
    strSql = "SELECT B_RecordID,B_Filename FROM [Block] ORDER BY B_RecordID ASC"
    c.execute(strSql)
    rows = c.fetchall()
    for row in rows:
        strSql2 = "SELECT TI_RecordID FROM TransactionInfo WHERE TI_B_RecordID=%s" % (
            row[0])
        c.execute(strSql2)
        row2 = c.fetchone()
        if row2 is None:
            # 插入到数据库中
            blockFilePath = BlockDir+'/' + row[1]
            hashList = FindAllTranHashes(blockFilePath)
            for strHash in hashList:
                strSqlInsert = "INSERT INTO TransactionInfo(TI_TranHash,TI_B_RecordID) VALUES('%s',%s)" % (
                    strHash, row[0])
                c.execute(strSqlInsert)
    # 提交
    dbConn.commit()
    dbConn.close()
    return


def CheckTranHash(strTranHash):
    # 检查strTranHash是否存在数据库TransactionInfo中
    dbFilename = BlockDbFilename
    dbConn = sqlite3.connect(dbFilename)  # 数据库连接
    c = dbConn.cursor()
    strSql = "SELECT TI_RecordID FROM TransactionInfo WHERE TI_TranHash='%s' " % (
        strTranHash)
    rtnExisted = False
    c.execute(strSql)
    intRows = len(c.fetchall())
    if intRows > 0:
        rtnExisted = True

    dbConn.close()
    return


def GenerateZeroBlock():  # 生成0号区块
    global dsBlockPart1, dsBlockPart2, BlockDir
    # 生产0号/创世区块
    blockId = 0
    _dsBlockPart1 = dsBlockPart1.copy()
    _dsBlockPart1['blockId'] = 0
    _dsBlockPart1['previousBlockHash'] = 0
    _dsBlockPart1['blockTranStr'] = ''
    _dsBlockPart1['minerAddress'] = ''
    _dsBlockPart1['miningMsg'] = 'FirstBlock'
    _dsBlockPart1['miningDifficulty'] = 1
    _dsBlockPart1['timestamp'] = int(time.time())  # 时间戳
    miningRandomNum = 0
    while miningRandomNum <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:
        _dsBlockPart1['miningRandomNum'] = miningRandomNum
        blockStr1 = json.dumps(_dsBlockPart1)
        blockHash = Crypto.Hash.SHA256.new()
        blockHash.update(blockStr1.encode('utf-8'))
        blockHashStr = blockHash.hexdigest()
        if int(blockHashStr, 16) <= DftTheta:
            _dsBlockPart2 = dsBlockPart2.copy()
            _dsBlockPart2['part1Str'] = blockStr1
            _dsBlockPart2['thisBlockHash'] = blockHashStr
            blockStr2 = json.dumps(_dsBlockPart2)
            newBlockFile = r"%s\block_%s_%s.txt" % (
                BlockDir, blockId, blockHashStr)
            f = open(newBlockFile, "w+")
            f.write(blockStr2)
            f.close()
            break
        miningRandomNum = miningRandomNum + 1
    return


def GetLastBlockInfo():  # 找到最后一个区块的ID和Hash值
    global dsBlockPart1, dsBlockPart2

    global BlockDbFilename
    dbFilename = BlockDbFilename
    dbConn = sqlite3.connect(dbFilename)  # 数据库连接
    c = dbConn.cursor()
    # 从区块数据库中找出最大的一个区块
    strSql = "SELECT MAX(B_BlockID),B_BlockHash,B_Filename FROM [Block]"
    #保证该区块不是在被启用的分支上

    c.execute(strSql)
    rows = c.fetchone()  # 可能有多个区块具有相同的最大的BlockID，因此选择其中的一个，用来矿工打包区块
    if len(rows) == 0:
        blockId = 0
        blockHashStr = ''
        blockFilename = ''
    else:
        blockId = int(rows[0])
        blockHashStr = rows[1]
        blockFilename = rows[2]

    dbConn.close()
    return int(blockId), blockHashStr, blockFilename


def AdjustMiningDifficulty(miningPhase):  # 根据指定的区块编号调整挖矿难度
    global MiningDifficulty, DftTheta  # 引用全局变量
    global BlockDbFilename
    dbFilename = BlockDbFilename
    dbConn = sqlite3.connect(dbFilename)  # 数据库连接
    c = dbConn.cursor()

    # 查找是否已经存在
    strSqlSelect = "SELECT MD_Phase,MD_MiningDifficulty FROM MiningDifficulty WHERE MD_Phase=%s" % (
        miningPhase)
    c.execute(strSqlSelect)
    rows = c.fetchall()
    if len(rows) > 0:
        #已存在
        MiningDifficulty = int(rows[0][1])
    else:
        if (miningPhase == 0):
            # 已有区块数小于100，不用调整
            #将难度值保存到数据库中
            strSqlInsert = "INSERT INTO MiningDifficulty(MD_Phase,MD_MiningDifficulty) VALUES(%s,%s)" % (
                0, MiningDifficulty)
            c.execute(strSqlInsert)
            dbConn.commit()
        else:  # miningPhase > 0
            #读取数据库中的记录，根据区块时间戳和区块个数来调整挖矿难度,使用最近的100个区块
            strSql = "SELECT [B_Filename], [B_BlockID], [B_BlockHash], [B_Timestamp], [B_PrevBlockID],[B_PrevBlockHash],[B_Confirmed] FROM [Block] WHERE [B_BlockID] IN (%s,%s) ORDER BY [B_BlockID] DESC" % (
                miningPhase*100, (miningPhase-1)*100+1)
            c.execute(strSql)
            rows = c.fetchall()
            blockCount = len(rows)
            timestampEnd = rows[0][3]
            timestampStart = rows[blockCount-1][3]

            t = abs(float(timestampEnd) - float(timestampStart))  # 总用时
            difOld = MiningDifficulty
            difNew = difOld*600000/t
            if difNew > difOld*1.3:
                difNew = difOld*1.3
            if difNew < difOld*0.7:
                difNew = difOld*0.7
            MiningDifficulty = int(difNew)

            #将难度值保存到数据库中
            strSqlInsert = "INSERT INTO MiningDifficulty(MD_Phase,MD_MiningDifficulty) VALUES(%s,%s)" % (
                miningPhase, MiningDifficulty)
            c.execute(strSqlInsert)
            dbConn.commit()

    dbConn.close()

    DftTheta = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF >> MiningDifficulty  # 右移
    return


def VerifyBlock(blockFileName):  # 验证区块
    # 从交易文件中导入交易信息
    f = open(blockFileName, "r")
    str1 = f.read()  # 每个区块文件只包含一笔交易
    f.close()
    rstValue = False
    if len(str1) > 0:
        try:
            from Crypto.Hash import SHA256
            rstValue = False
            _dsBlockPart2 = json.loads(str1)
            # calHash = Crypto.Hash.SHA256.new()
            calHash = SHA256.new()
            calHash.update(_dsBlockPart2['part1Str'].encode('utf-8'))
            calHashStr = calHash.hexdigest()
            if calHashStr == _dsBlockPart2['thisBlockHash']:
                rstValue = True
            # 只验证了本区块的HASH值是否合法，还可以
            # 验证该区块指向的上一区块指针是否合法
            # 验证区块的难度是否合法
            # 验证区块时间戳是否合法
            # coinbase是否合法
            # 验证每一笔交易是否合法
            # 验证矿工手续费是否合法
        except():
            pass
    return rstValue


def ConfirmBlocksInDb():  # 确认数据库中的区块
    global BlockDbFilename
    # 后续有6个块的情况下才认为转账到账.
    dbFilename = BlockDbFilename
    dbConn = sqlite3.connect(dbFilename)  # 数据库连接
    c = dbConn.cursor()
    lastBlockId, lastBlockHash, lastBlockFilename = GetLastBlockInfo()
    if lastBlockId < 6:
        return
    blockId = lastBlockId
    hashStr = lastBlockHash
    # B_Filename,B_BlockID,B_BlockHash,B_Timestamp,B_PrevBlockID,B_PrevBlockHash,B_Confirmed
    for i in range(0, 6, 1):  # 往前找6个块
        strSqlSearch = "SELECT B_BlockID,B_BlockHash FROM [Block] WHERE B_BlockHash=(SELECT B_PrevBlockHash FROM [Block] WHERE B_BlockHash='%s')  " % (
            hashStr)
        c.execute(strSqlSearch)
        row = c.fetchone()
        blockId = row[0]
        hashStr = row[1]
    #从找到的块继续回溯
    for j in range(0, blockId, 1):
        strSqlSearch = "SELECT B_BlockID,B_BlockHash FROM [Block] WHERE B_BlockHash=(SELECT B_PrevBlockHash FROM [Block] WHERE B_BlockHash='%s')  " % (
            hashStr)
        c.execute(strSqlSearch)
        row = c.fetchone()
        blockId = row[0]
        hashStr = row[1]
        #将找到的块进行确认，并将其兄弟块标记为弃用
        strSqlUpdate = "UPDATE [Block] SET B_Confirmed=1 WHERE (B_BlockHash='%s')" % (
            hashStr)
        c.execute(strSqlUpdate)
        strSqlUpdate = "UPDATE [Block] SET B_Forked=1 WHERE (B_BlockID=%s) AND (B_BlockHash<>'%s')" % (
            blockId, hashStr)
        c.execute(strSqlUpdate)
        dbConn.commit()

    dbConn.close()
    return


def GetAccountCredit(address=''):
    global BlockDbFilename
    global BlockDir

    dbFilename = BlockDbFilename
    dbConn = sqlite3.connect(dbFilename)  # 数据库连接
    c = dbConn.cursor()
    lastBlockID = 0
    lastTokenCount = 0.0

    # 获取上次计算的信息
    #AccountInfo  AI_RecordID   AI_AccountAddress   AI_BlockID   AI_TokenCount
    strSqlSearch = "SELECT AI_BlockID,AI_TokenCount FROM AccountInfo WHERE AI_AccountAddress='%s'" % (
        address)
    c.execute(strSqlSearch)
    row = c.fetchone()
    if row is not None:
        lastBlockID = int(row[0])
        lastTokenCount = float(row[1])
    # 找出未计算、已确认的区块
    # Block B_Filename,B_BlockID,B_BlockHash,B_Timestamp,B_PrevBlockID,B_PrevBlockHash,B_Confirmed,B_Forked
    strSqlSearch = "SELECT B_BlockID,B_Filename FROM Block WHERE B_Confirmed=1 AND B_Forked=0 AND B_BlockID>%s ORDER BY B_BlockID ASC" % (
        lastBlockID)
    c.execute(strSqlSearch)
    rows = c.fetchall()
    rstValue = lastTokenCount  # 从上次结束的地方开始计算
    countBlockID = lastBlockID
    for row in rows:
        blocFilename = BlockDir+'/'+row[1]
        vResult = VerifyBlock(blocFilename)
        if vResult:
            f = open(blocFilename, "r")
            str1 = f.read()  # 每个区块文件只包含一笔交易
            f.close()
            if len(str1) > 0:
                _dsBlockPart2 = json.loads(str1)
                __dsBlockPart1 = json.loads(_dsBlockPart2['part1Str'])
                listTranStrs = __dsBlockPart1.get('blockTranStr')  # 将各笔交易分开
                for aStr in listTranStrs:  # 针对每一笔交易进行处理
                    _dsTranPart3 = json.loads(aStr)
                    _dsTranPart2 = json.loads(_dsTranPart3['part2Str'])
                    _dsTranPart1 = json.loads(_dsTranPart2['part1Str'])
                    if _dsTranPart1['sender'] == address:
                        rstValue -= float(_dsTranPart1['value'])  # 支出
                        rstValue -= float(_dsTranPart1['fee'])  # 交易费支出
                    if _dsTranPart1['recipient'] == address:
                        rstValue += float(_dsTranPart1['value'])  # 转入
                countBlockID = row[0]  # 最后计算到了哪个区块号
    #保存结果
    #AccountInfo  AI_RecordID   AI_AccountAddress   AI_BlockID   AI_TokenCount
    strSqlUpdate = "INSERT OR REPLACE INTO AccountInfo(AI_BlockID,AI_TokenCount,AI_AccountAddress)  VALUES(%s,%s,'%s')" % (countBlockID,
                                                                                                                           rstValue,  address)
    c.execute(strSqlUpdate)
    dbConn.commit()

    print("账户:%s 的余额为:%.8f" % (address, rstValue))
    dbConn.close()
    return rstValue


class Student:
    Name = ''
    address = ''
    Credits = 0
    private_key = ''  # 修改为可以外部访问
    public_key = ''  # 修改为可以外部访问

    def __init__(self, stuName):
        PrivateFile = accountDir+"/%s_Private.pem" % (stuName)
        if os.path.exists(PrivateFile):
            # 导入已有账户的私钥
            with open(PrivateFile, "rb") as f:
                privatePemStr = f.read()
                self.private_key = Crypto.PublicKey.RSA.importKey(
                    privatePemStr)
        else:
            # 创建新的账户
            random = Crypto.Random.new().read
            self.private_key = RSA.generate(1024, random)
            # 保存密钥
            with open(PrivateFile, "wb") as f:
                privatePemStr = self.private_key.exportKey()   # 导出私钥
                f.write(privatePemStr)
                f.close()

        publickFile = accountDir+"/%s_Public.pem" % (stuName)
        if os.path.exists(publickFile):
            # 导入已有账户的公钥
            with open(publickFile, "rb") as f:
                publicPemStr = f.read()
                self.public_key = Crypto.PublicKey.RSA.importKey(publicPemStr)
        else:
            self.public_key = self.private_key.publickey()
            publicPemStr = self.public_key.exportKey()   # 导出公钥
            with open(publickFile, "wb") as f:
                f.write(publicPemStr)
        # 计算出账户
        str1 = self.public_key.exportKey(format='PEM').decode('utf-8')
        h1 = Crypto.Hash.SHA256.new(str1.encode('utf-8'))
        str2 = binascii.hexlify(h1.digest()).decode('utf-8')
        h2 = Crypto.Hash.SHA256.new(str2.encode('utf-8'))
        str3 = binascii.hexlify(h2.digest()).decode('utf-8')
        self.address = str3.upper()
        # print("Account name: %s address: %s balance: %s" % (stuName,self.address,GetAccountCredit(self.address)))


class Miner(Student):  # 继承
    # 铸币交易转账，给矿工提供奖励,转账人为coinbase，没有签名
    def CoinbaseTransfer(self, minerAddress, value):
        self.timestamp = int(time.time())
        _dsTranPart1 = dsTranPart1.copy()
        _dsTranPart1['sender'] = 'coinbase'
        _dsTranPart1['recipient'] = minerAddress
        _dsTranPart1['value'] = value
        _dsTranPart1['timestamp'] = self.timestamp
        _dsTranPart1['scriptstr'] = 'script string'
        _dsTranPart1['specinfo'] = 'specific information'

        _dsTranPart2 = dsTranPart2.copy()
        _dsTranPart2['part1Str'] = json.dumps(_dsTranPart1)
        _dsTranPart2['signStr'] = ''
        _dsTranPart2['pubkeyStr'] = ''

        _dsTranPart3 = dsTranPart3.copy()
        _dsTranPart3['part2Str'] = json.dumps(_dsTranPart2)
        trnHash = Crypto.Hash.SHA256.new()
        trnHash.update(_dsTranPart3['part2Str'].encode('utf-8'))
        tranHashStr = trnHash.hexdigest()
        _dsTranPart3['tranHash'] = tranHashStr
        strReturn = json.dumps(_dsTranPart3)
        return strReturn

    # 矿工将手续费装给自己,fee包括整个区块中的转账手续费
    def FeeTransfer(self, minerAddress, fee):
        self.timestamp = int(time.time())
        _dsTranPart1 = dsTranPart1.copy()
        _dsTranPart1['sender'] = 'fee'
        _dsTranPart1['recipient'] = minerAddress
        _dsTranPart1['value'] = fee
        _dsTranPart1['timestamp'] = self.timestamp
        _dsTranPart1['scriptstr'] = 'script string'
        _dsTranPart1['specinfo'] = 'specific information'

        _dsTranPart2 = dsTranPart2.copy()
        _dsTranPart2['part1Str'] = json.dumps(_dsTranPart1)
        _dsTranPart2['signStr'] = ''
        _dsTranPart2['pubkeyStr'] = ''

        _dsTranPart3 = dsTranPart3.copy()
        _dsTranPart3['part2Str'] = json.dumps(_dsTranPart2)
        trnHash = Crypto.Hash.SHA256.new()
        trnHash.update(_dsTranPart3['part2Str'].encode('utf-8'))
        tranHashStr = trnHash.hexdigest()
        _dsTranPart3['tranHash'] = tranHashStr
        strReturn = json.dumps(_dsTranPart3)
        return strReturn
    # 矿工，负责验证交易，并把验证通过的交易记录到账本中

    def VerifyTransAndMining(self, tranFilename):
        global dsBlockPart1, dsBlockPart2, dsTranPart1, dsTranPart2, dsTranPart3
        # region 挖矿前的准备工作
        UpdateBlockDb()  # 挖矿之前先更新区块数据库
        previousBlockId, previousBlockHash, previousBlockFilename = GetLastBlockInfo()
        miningPhase = (previousBlockId) // 100
        AdjustMiningDifficulty(miningPhase)  # 设置挖矿难度
        ConfirmBlocksInDb()  # 确认之前的块
        UpdateTransactionInfoDb()        # 更新交易信息数据库
        # endregion

        # 构造转账给矿工的交易，实现矿工奖励
        coinbaseTranStr = self.CoinbaseTransfer(self.address, 100)  # 铸币交易字符串
        # fullTranStrs为完整的交易字符串，里边包含coinbase和其他的多笔转账交易
        fullTranStrs = [coinbaseTranStr]

        # region #从交易文件中导入交易信息
        TransactionList = []
        if os.path.exists(tranFilename):
            f = open(tranFilename, "r")
            str1 = f.read()
            f.close()
            os.remove(tranFilename)
            TransactionList.append(str1)
        # endregion
        # 验证TransactionList中的交易记录，如果正确则保存到交易记录账本中
        boolVerified = True
        floatTotalFee = 0
        for aStr in TransactionList:
            if len(aStr) > 0:
                _dsTranPart1 = dsTranPart1.copy()
                _dsTranPart2 = dsTranPart2.copy()
                _dsTranPart3 = dsTranPart3.copy()
                _dsTranPart3 = json.loads(aStr)
                # 检查_dsTranPart3['tranHash'] 是否存在数据库TransactionInfo中，若存在则表示已经处理过了，不再重复处理
                if CheckTranHash(_dsTranPart3['tranHash']):
                    continue
                _dsTranPart2 = json.loads(_dsTranPart3['part2Str'])
                _dsTranPart1 = json.loads(_dsTranPart2['part1Str'])
                # 验证交易HASH是否正确
                trnHash = Crypto.Hash.SHA256.new()
                trnHash.update(_dsTranPart3['part2Str'].encode('utf-8'))
                tranHashStr = trnHash.hexdigest()
                if _dsTranPart3['tranHash'] == tranHashStr:
                    # 交易HASH验证通过
                    pubkeyStr = _dsTranPart2['pubkeyStr']
                    signStr = _dsTranPart2['signStr']
                    pubkeyPem = base64.b32decode(pubkeyStr)
                    keyPub = Crypto.PublicKey.RSA.importKey(pubkeyPem)
                    tranVerifer = Crypto.Signature.PKCS1_v1_5.new(keyPub)
                    calTanHash = Crypto.Hash.SHA256.new()
                    calTanHash.update(json.dumps(_dsTranPart1).encode('utf-8'))
                    boolTmp = tranVerifer.verify(
                        calTanHash, base64.b32decode(signStr))  # 使用公钥验证签名
                    if boolTmp:  # 验证通过
                        fullTranStrs.append(json.dumps(_dsTranPart3))
                        # 计算转账手续费
                        floatTotalFee = floatTotalFee + \
                            float(_dsTranPart1['fee'])
                    else:
                        boolVerified = False  # 有一笔交易为验证通过，则放弃处理
        feeTranStr = self.FeeTransfer(self.address, floatTotalFee)  # 铸币交易字符串
        fullTranStrs.append(feeTranStr)
        if boolVerified:  # 支付签名验证通过,开始打包为block
            blockId = int(previousBlockId) + 1
            _dsBlockPart1 = dsBlockPart1.copy()
            _dsBlockPart1['blockId'] = blockId
            _dsBlockPart1['previousBlockHash'] = previousBlockHash
            _dsBlockPart1['blockTranStr'] = fullTranStrs
            _dsBlockPart1['minerAddress'] = self.address
            _dsBlockPart1['miningMsg'] = 'f'
            _dsBlockPart1['miningDifficulty'] = MiningDifficulty
            _dsBlockPart1['timestamp'] = int(time.time())  # 时间戳
            miningRandomNum = 0
            while miningRandomNum <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:
                if miningRandomNum % 100000:
                    #检查在挖矿过程中，是否已经有其他矿工挖到该区块了
                    _previousBlockId, _previousBlockHash, _previousBlockFilename = GetLastBlockInfo()
                    if int(_previousBlockId) >= blockId:
                        break  # 已有其他矿工挖到区块，跳出，不再针对该区块进行挖矿
                _dsBlockPart1['miningRandomNum'] = miningRandomNum
                blockStr1 = json.dumps(_dsBlockPart1)
                blockHash = Crypto.Hash.SHA256.new()
                blockHash.update(blockStr1.encode('utf-8'))
                blockHashStr = blockHash.hexdigest()
                if int(blockHashStr, 16) <= DftTheta:
                    dsBlockPart2['part1Str'] = blockStr1
                    dsBlockPart2['thisBlockHash'] = blockHashStr
                    blockStr2 = json.dumps(dsBlockPart2)
                    newBlockFile = r"%s\block_%s_%s.txt" % (
                        BlockDir, blockId, blockHashStr)
                    f = open(newBlockFile, "w+")
                    f.write(blockStr2)
                    f.close()
                    _TimeStr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 
                    print("A block found. %s" % _TimeStr)
                    break
                miningRandomNum = miningRandomNum + 1
        return


class Transaction:
    def __init__(self, stu):
        self._private_key = stu.private_key
        self._private_key_str = base64.b32encode(
            self._private_key.exportKey(format='PEM'))
        self._public_key = stu.public_key
        self._public_key_str = base64.b32encode(
            self._public_key.exportKey(format='PEM'))
        return

    # 转账交易，sender：发送者；recipient：接收者；value：转账数量；fee：转账手续费

    # 发起转账交易，直接转账到一个地址上
    def TransferToAddress(self, sender, recipientAddress='', value=0, fee=0):
        self.timestamp = int(time.time())
        # 验证输入是否合法
        if float(value) <= 0 or float(fee) <= 0:
            print("转账输入的数量不正确。")
            return -1
        # 验证是否具有足够的余额进行转账
        floatCredit = GetAccountCredit(sender.address)
        if floatCredit < (float(value)+float(fee)):
            print("余额不足。账户:%s 的余额为:%.8f" % (sender.address, floatCredit))
            return -2
        dsTranPart1['sender'] = sender.address
        dsTranPart1['recipient'] = recipientAddress
        dsTranPart1['value'] = value
        dsTranPart1['fee'] = fee
        dsTranPart1['timestamp'] = self.timestamp
        dsTranPart1['scriptstr'] = '<attachfile>49fc2b67fc45787a90a0dc1eb6873ddc396c1c00</attachfile>'
        dsTranPart1['specinfo'] = LoadCsiJsonStr()

        dsTranPart2['part1Str'] = json.dumps(dsTranPart1)
        signStr = self.SignTransaction(dsTranPart2['part1Str'])
        dsTranPart2['signStr'] = signStr
        dsTranPart2['pubkeyStr'] = self._public_key_str.decode('utf-8')
        dsTranPart3['part2Str'] = json.dumps(dsTranPart2)
        trnHash = Crypto.Hash.SHA256.new()
        trnHash.update(dsTranPart3['part2Str'].encode('utf-8'))
        tranHashStr = trnHash.hexdigest()
        dsTranPart3['tranHash'] = tranHashStr
        str2 = json.dumps(dsTranPart3)
        tranFilename = BlockDir + \
            '/tran_%s_%s.txt' % (self.timestamp, tranHashStr)
        self.TransactionSave(tranFilename, str2)
        return

    def Transfer(self, sender, recipient, value, fee):
        self.TransferToAddress(sender, recipient.address, value, fee)
        return

    # 存储交易信息
    def TransactionSave(self, tranFilename, tranStr):
        # 输入文件名，和交易字符串
        f = open(tranFilename, "a+")
        f.write(tranStr)
        f.close()
        return
    # 返回签名后的字符串, tranStr为源字符串、没有经过hash处理

    def SignTransaction(self, tranStr=''):
        signer = PKCS1_v1_5.new(self._private_key)
        h = Crypto.Hash.SHA256.new(tranStr.encode('utf-8'))
        hStr = h.hexdigest()
        str2 = signer.sign(h)
        str3 = base64.b32encode(str2).decode('utf-8')
        return str3


# 账户名和命令不区分大小写
helpStr = '\n> help：Show all commands.'
helpStr += '\n> list：Show all exits accounts.'
helpStr += '\n> refresh：Update local database through local blocks.'
helpStr += '\n> create：Create a new account. Format：create account_name'
helpStr += '\n> use：Use specified account. If specified account is not existed, it will be created. Format：use account_name'
helpStr += '  \n>>  back: Go back to main menu.'
helpStr += '  \n>>  info: Show the account information.'
helpStr += '  \n>>  transfer: Transfer token to an address. Format：transfer receiver_address value fee'
helpStr += '  \n>>  mine: Run as a miner and begin to work.'
helpStr += '\n> exit: Exit system.'
cmdStr = '\n> Input command: '
cmdStr2 = '\n>> Input command: '
reqStr = FilterInput(input(helpStr+cmdStr))

keepMining = False # 是否挖矿，用于控制是否启动挖矿程序


def CMD_List():
    allAccounts = FindAllAccounts(accountDir)
    for accountNameStr in allAccounts:
        stuTmp = Student(accountNameStr)
        print('Account name: %s Address: %s Balance: %s' % (
            accountNameStr, stuTmp.address, GetAccountCredit(stuTmp.address)))

while reqStr != 'exit':
    userInput = reqStr.split(' ')

    if userInput[0].lower() == 'help':  # 帮助
        print(helpStr)

    if userInput[0].lower() == 'list':  # 列出所有的账户
        CMD_List()

    if userInput[0].lower() == 'refresh':
        UpdateBlockDb()  # 挖矿之前先更新区块数据库
        previousBlockId, previousBlockHash, previousBlockFilename = GetLastBlockInfo()
        miningPhase = (previousBlockId) // 100
        AdjustMiningDifficulty(miningPhase)  # 设置挖矿难度
        ConfirmBlocksInDb()  # 确认之前的块
        UpdateTransactionInfoDb()        # 更新交易信息数据库
        CMD_List() #显示已有账户

    if userInput[0].lower() == 'create':  # 创建一个新账户
        if len(userInput) < 2:
            print('Account name is not valid.')
        else:
            accountNameStr = userInput[1]
            if CheckAccountName(accountNameStr):  # 账户名合法
                # 检查该账户是否已经存在
                allAccounts = FindAllAccounts(accountDir)
                accountExisted = False
                for accoutA in allAccounts:
                    if accountNameStr.lower() == accoutA.lower():
                        accountExisted = True
                if accountExisted:
                    print('The account is exisited, please try a new name')
                else:
                    # 创建
                    aNewAccount = Student(accountNameStr)
                    print('created successfully.')
                allAccounts = FindAllAccounts(accountDir)
                print('Existed local accounts: %s' % allAccounts)
            else:
                print('Account name is not valid.')

    if userInput[0].lower() == 'use':  # 使用指定的账户
        accountNameStr = userInput[1]
        if CheckAccountName(accountNameStr):  # 账户名合法
            accountUsed = Student(accountNameStr)
            print('Open account successfully.')
            print('Account info, name: %s Address: %s Balance: %s' % (
                accountNameStr, accountUsed.address, GetAccountCredit(accountUsed.address)))
            reqStr2 = FilterInput(input(cmdStr2))  # 等待命令
            while reqStr2 != 'exit':
                userInput2 = reqStr2.split(' ')
                if userInput2[0].lower() == 'back':
                    break
                if userInput2[0].lower() == 'info':
                    print('Account info, name: %s Address: %s Balance: %s' % (
                        accountNameStr, accountUsed.address, GetAccountCredit(accountUsed.address)))
                if userInput2[0].lower() == 'transfer':
                    receiverAddressStr = userInput2[1]
                    valueStr = userInput2[2]
                    feeStr = userInput2[3]
                    tran1 = Transaction(accountUsed)    # StuA发起的交易
                    tran1.TransferToAddress(
                        accountUsed, receiverAddressStr, float(valueStr), float(feeStr))
                    print('Transfer successfully.')
                if userInput2[0].lower() == 'mine':
                    if not keepMining:  
                        def MinerMine():
                            while keepMining:     
                                minerA = Miner(accountNameStr)
                                fList = FindTranFiles(BlockDir)
                                if len(fList) == 0:
                                    minerA.VerifyTransAndMining('')
                                for f in fList:
                                    minerA.VerifyTransAndMining(f)   
                                time.sleep(3)
                                # print('mining.')
                            return
                        keepMining = True
                        miningThread = threading.Thread(target=MinerMine)
                        miningThread.start()                        
                        print('start mining.')
                if userInput2[0].lower() == 'stop':
                     keepMining = False
                     print('mining stopped.')
                if userInput2[0].lower() == 'exit':
                    os._exit()                  

                reqStr2 = FilterInput(input(cmdStr2))  # 等待命令
        else:
            print('Account name is not valid.')

    reqStr = FilterInput(input(cmdStr))
print('Application terminated.')